Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit d66d44ae28d89249054432554d749da53b284fac


Parents : c682743
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-07-08T13:07:07-05:00

feat(Self-Test): implement system self-check functionality with diagnostics and UI integration

Changes
Diff

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ddab9499..4b2ec7df 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -305,3 +305,30 @@ jobs:
- name: Run Playwright smoke E2E
run: bash scripts/ci/github-e2e.sh
+
+ self-check:
+ name: Headless Self-Check (${{ matrix.os }})
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 15
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ permissions:
+ contents: read
+ steps:
+ - name: Checkout
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
+
+ - name: Set up development environment
+ uses: ./.github/actions/setup-dev-environment
+ with:
+ python-version: ${{ env.PYTHON_VERSION }}
+ uv-version: ${{ env.UV_VERSION }}
+ node-version: ${{ env.NODE_VERSION }}
+ pnpm-version: ${{ env.PNPM_VERSION }}
+
+ - name: Run headless system self-check
+ shell: bash
+ run: |
+ uv run python -m meshchatx.meshchat --self-check --headless

diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py
index 9573e4ac..8644f630 100644
--- a/meshchatx/meshchat.py
+++ b/meshchatx/meshchat.py
@@ -753,6 +753,105 @@ class ReticulumMeshChat:
if self.current_context:
self.current_context.storage_path = value
+ def run_self_test(self) -> dict:
+ stack_ok = True
+ stack_reason = ""
+ if not hasattr(self, "reticulum") or self.reticulum is None:
+ stack_ok = False
+ stack_reason = "Reticulum stack is not initialized"
+ else:
+ try:
+ _ = self.reticulum.config
+ except Exception as e:
+ stack_ok = False
+ stack_reason = f"Reticulum stack internal error: {str(e)}"
+
+ config_ok = True
+ config_reason = ""
+ try:
+ if self.config is None:
+ config_ok = False
+ config_reason = "App configuration is not initialized"
+ else:
+ _ = self.config.display_name.get()
+ except Exception as e:
+ config_ok = False
+ config_reason = f"App config error: {str(e)}"
+
+ if config_ok:
+ try:
+ reticulum_config_path = self._api_reticulum_config_path()
+ if not reticulum_config_path or not os.path.exists(reticulum_config_path):
+ config_ok = False
+ config_reason = "Reticulum config file not found"
+ else:
+ if not reticulum_config_has_required_sections(reticulum_config_path):
+ config_ok = False
+ config_reason = "Reticulum config is missing required sections"
+ except Exception as e:
+ config_ok = False
+ config_reason = f"Reticulum config check failed: {str(e)}"
+
+ db_ok = True
+ db_reason = ""
+ if not self.database:
+ db_ok = False
+ db_reason = "Database is not initialized"
+ else:
+ try:
+ self.database.execute_sql("SELECT 1").fetchone()
+ snapshot = self.database.get_database_health_snapshot()
+ if snapshot.get("quick_check") not in ("ok", "unknown"):
+ db_ok = False
+ db_reason = f"Database quick check returned: {snapshot.get('quick_check')}"
+ except Exception as e:
+ db_ok = False
+ db_reason = f"Database check failed: {str(e)}"
+
+ rw_ok = True
+ rw_reason = ""
+ try:
+ if not self.storage_path or not os.path.exists(self.storage_path):
+ rw_ok = False
+ rw_reason = "Storage directory does not exist"
+ else:
+ temp_file_path = os.path.join(self.storage_path, ".self_test_temp")
+ test_data = "meshchatx_self_test_write_read_verify"
+ with open(temp_file_path, "w", encoding="utf-8") as f:
+ f.write(test_data)
+
+ with open(temp_file_path, "r", encoding="utf-8") as f:
+ read_data = f.read()
+
+ if os.path.exists(temp_file_path):
+ os.remove(temp_file_path)
+
+ if read_data != test_data:
+ rw_ok = False
+ rw_reason = "Read data did not match written data"
+ except Exception as e:
+ rw_ok = False
+ rw_reason = f"Read/write test failed: {str(e)}"
+
+ return {
+ "stack_up": {
+ "status": "ok" if stack_ok else "failed",
+ "reason": stack_reason,
+ },
+ "config_good": {
+ "status": "ok" if config_ok else "failed",
+ "reason": config_reason,
+ },
+ "db_good": {
+ "status": "ok" if db_ok else "failed",
+ "reason": db_reason,
+ },
+ "read_write_good": {
+ "status": "ok" if rw_ok else "failed",
+ "reason": rw_reason,
+ },
+ }
+
@property
def database_path(self):
return self.current_context.database_path if self.current_context else None
@@ -4404,6 +4503,11 @@ class ReticulumMeshChat:
},
)
+ @routes.get("/api/v1/self-test")
+ async def self_test(request):
+ results = self.run_self_test()
+ return web.json_response(results)
+
@routes.get("/api/v1/server/security")
async def server_security_get(request):
settings = load_app_security_settings(self.storage_dir)
@@ -19996,6 +20100,13 @@ def main():
help="Disable the plugin system entirely. Can also be set via MESHCHAT_DISABLE_PLUGINS environment variable.",
)
+ parser.add_argument(
+ "--self-check",
+ action="store_true",
+ default=env_bool("MESHCHAT_SELF_CHECK", False),
+ help="Run system self-check diagnostics on startup and then exit with 0 if all checks pass, or 1 if any fail. Can also be set via MESHCHAT_SELF_CHECK environment variable.",
+ )
+
args = parser.parse_args()
ssl_cert = (args.ssl_cert or "").strip() or None
@@ -20157,6 +20268,39 @@ def main():
reticulum_config_dir=reticulum_meshchat.reticulum_config_dir,
)
+ if args.self_check:
+ results = reticulum_meshchat.run_self_test()
+ print("\n================================")
+ print(" System Self-Check Results")
+ print("================================")
+
+ all_passed = True
+ labels = {
+ "stack_up": "Network Stack ",
+ "config_good": "Configuration Integrity",
+ "db_good": "Database Connection ",
+ "read_write_good": "Storage Read/Write ",
+ }
+
+ for key, name in labels.items():
+ check = results.get(key, {"status": "failed", "reason": "No result"})
+ if check["status"] == "ok":
+ print(f"[OK] {name}")
+ else:
+ all_passed = False
+ reason = check.get("reason") or "Unknown error"
+ print(f"[FAILED] {name} - Reason: {reason}")
+
+ print("================================")
+ if all_passed:
+ print("Status: SUCCESS (All checks passed)")
+ print("================================\n")
+ sys.exit(0)
+ else:
+ print("Status: FAILED")
+ print("================================\n")
+ sys.exit(1)
+
if args.reset_password:
if reticulum_meshchat.reset_password():
print("Password has been reset. Set a new password via the web UI.")

diff --git a/meshchatx/src/frontend/components/settings/SettingsPage.vue b/meshchatx/src/frontend/components/settings/SettingsPage.vue
index 218fd655..71a6d8f2 100644
--- a/meshchatx/src/frontend/components/settings/SettingsPage.vue
+++ b/meshchatx/src/frontend/components/settings/SettingsPage.vue
@@ -683,6 +683,88 @@
</div>
</section>
+ <section v-show="showSection('selftest')" class="settings-section break-inside-avoid">
+ <header class="settings-section__header">
+ <div>
+ <div class="settings-section__eyebrow">Maintenance</div>
+ <h2>{{ $t("selftest.title") }}</h2>
+ <p>{{ $t("selftest.description") }}</p>
+ </div>
+ </header>
+ <div class="settings-section__body space-y-4">
+ <div class="flex items-center gap-3">
+ <button
+ type="button"
+ class="px-4 py-2 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-xl transition flex items-center gap-2"
+ :disabled="selfTestRunning"
+ @click="runSelfTest"
+ >
+ <MaterialDesignIcon
+ v-if="selfTestRunning"
+ icon-name="loading"
+ class="animate-spin size-4"
+ />
+ <MaterialDesignIcon v-else icon-name="play-circle-outline" class="size-4" />
+ {{ selfTestRunning ? $t("selftest.running") : $t("selftest.run_test_btn") }}
+ </button>
+ </div>
+
+ <div
+ v-if="selfTestResults"
+ class="space-y-3 mt-4 border-t border-gray-100 dark:border-zinc-800 pt-4"
+ >
+ <div
+ v-for="check in selfTestChecks"
+ :key="check.key"
+ class="flex flex-col p-3 rounded-xl border bg-white dark:bg-zinc-900"
+ :class="
+ check.passed
+ ? 'border-emerald-200/60 dark:border-emerald-900/30'
+ : 'border-red-200/60 dark:border-red-900/30'
+ "
+ >
+ <div class="flex items-center justify-between">
+ <div class="flex items-center gap-2 font-semibold text-sm">
+ <MaterialDesignIcon
+ :icon-name="
+ check.passed ? 'check-circle-outline' : 'alert-circle-outline'
+ "
+ :class="check.passed ? 'text-emerald-500' : 'text-red-500'"
+ class="size-4"
+ />
+ <span>{{ check.label }}</span>
+ </div>
+ <span
+ class="px-2 py-0.5 text-xs font-bold rounded-md"
+ :class="
+ check.passed
+ ? 'bg-emerald-50 dark:bg-emerald-950/20 text-emerald-700 dark:text-emerald-300'
+ : 'bg-red-50 dark:bg-red-950/20 text-red-700 dark:text-red-300'
+ "
+ >
+ {{ check.passed ? $t("selftest.passed") : $t("selftest.failed") }}
+ </span>
+ </div>
+ <div
+ v-if="!check.passed && check.reason"
+ class="text-xs text-red-600 dark:text-red-400 mt-2 pl-6"
+ >
+ <span class="font-semibold">{{ $t("selftest.reason_label") }}:</span>
+ {{ check.reason }}
+ </div>
+ </div>
+
+ <div
+ v-if="allSelfTestChecksPassed"
+ class="text-xs text-emerald-600 dark:text-emerald-400 font-semibold flex items-center gap-2 pl-2"
+ >
+ <MaterialDesignIcon icon-name="check" class="size-4" />
+ {{ $t("selftest.checks_completed") }}
+ </div>
+ </div>
+ </div>
+ </section>
+
<PluginsSettingsSection :visible="showSection('plugins')" />
<!-- Telephony Settings -->
@@ -2935,6 +3017,8 @@ export default {
gifImportReplaceDuplicates: false,
visualiserShowDisabledInterfaces: false,
visualiserShowDiscoveredInterfaces: false,
+ selfTestRunning: false,
+ selfTestResults: null,
};
},
computed: {
@@ -2956,6 +3040,40 @@ export default {
matchesSettingSearch(keywords, (k) => this.$t(k), this.searchQuery)
);
},
+ selfTestChecks() {
+ if (!this.selfTestResults) {
+ return [];
+ }
+ return [
+ {
+ key: "stack_up",
+ label: this.$t("selftest.stack_up"),
+ passed: this.selfTestResults.stack_up.status === "ok",
+ reason: this.selfTestResults.stack_up.reason,
+ },
+ {
+ key: "config_good",
+ label: this.$t("selftest.config_good"),
+ passed: this.selfTestResults.config_good.status === "ok",
+ reason: this.selfTestResults.config_good.reason,
+ },
+ {
+ key: "db_good",
+ label: this.$t("selftest.db_good"),
+ passed: this.selfTestResults.db_good.status === "ok",
+ reason: this.selfTestResults.db_good.reason,
+ },
+ {
+ key: "read_write_good",
+ label: this.$t("selftest.read_write"),
+ passed: this.selfTestResults.read_write_good.status === "ok",
+ reason: this.selfTestResults.read_write_good.reason,
+ },
+ ];
+ },
+ allSelfTestChecksPassed() {
+ return this.selfTestChecks.length > 0 && this.selfTestChecks.every((check) => check.passed);
+ },
safeConfig() {
if (!this.config) {
return {
@@ -3016,6 +3134,27 @@ export default {
this.loadVisualiserDisplayPrefsFromStorage();
},
methods: {
+ async runSelfTest() {
+ if (this.selfTestRunning) {
+ return;
+ }
+ this.selfTestRunning = true;
+ this.selfTestResults = null;
+ try {
+ const response = await window.api.get("/api/v1/self-test");
+ this.selfTestResults = response.data;
+ } catch (e) {
+ console.error("Failed to run system self-test", e);
+ this.selfTestResults = {
+ stack_up: { status: "failed", reason: e.message || String(e) },
+ config_good: { status: "failed", reason: e.message || String(e) },
+ db_good: { status: "failed", reason: e.message || String(e) },
+ read_write_good: { status: "failed", reason: e.message || String(e) },
+ };
+ } finally {
+ this.selfTestRunning = false;
+ }
+ },
loadVisualiserDisplayPrefsFromStorage() {
const p = loadVisualiserDisplayPrefs();
this.visualiserShowDisabledInterfaces = p.showDisabledInterfaces;

diff --git a/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js b/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
index 5892f30b..b94ffcc3 100644
--- a/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
+++ b/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
@@ -98,6 +98,7 @@ export const CORE_SETTINGS_SECTION_KEYWORDS = {
"Export Folders",
"Import Folders",
],
+ selftest: ["Self-Test", "System Self-Test", "diagnostic", "checks", "selftest.title", "selftest.description"],
desktop: [
"Desktop",
"App Behaviour",

diff --git a/meshchatx/src/frontend/js/settings/settingsTabs.js b/meshchatx/src/frontend/js/settings/settingsTabs.js
index 6ac89c78..95809fcf 100644
--- a/meshchatx/src/frontend/js/settings/settingsTabs.js
+++ b/meshchatx/src/frontend/js/settings/settingsTabs.js
@@ -47,7 +47,7 @@ export const SETTINGS_TABS = [
id: "maintenance",
labelKey: "settings.tabs.maintenance",
descriptionKey: "settings.tabs.maintenance_desc",
- sections: ["maintenance", "plugins"],
+ sections: ["maintenance", "selftest", "plugins"],
},
];

diff --git a/meshchatx/src/frontend/locales/de.json b/meshchatx/src/frontend/locales/de.json
index b5e5c392..debd5a56 100644
--- a/meshchatx/src/frontend/locales/de.json
+++ b/meshchatx/src/frontend/locales/de.json
@@ -695,6 +695,21 @@
"installing": "Plugin wird installiert..."
}
},
+ "selftest": {
+ "title": "System-Selbsttest",
+ "description": "Führen Sie Diagnoseprüfungen durch, um sicherzustellen, dass der Netzwerk-Stack, die Datenbank, die Konfigurationen und die Speicherberechtigungen funktionieren.",
+ "run_test_btn": "Diagnose ausführen",
+ "running": "Diagnose läuft...",
+ "stack_up": "Netzwerk-Stack",
+ "config_good": "Konfigurationsintegrität",
+ "db_good": "Datenbankverbindung",
+ "read_write": "Speicher Lese/Schreib",
+ "passed": "Bestanden",
+ "failed": "Fehlgeschlagen",
+ "status_label": "Status",
+ "reason_label": "Grund",
+ "checks_completed": "Alle Prüfungen erfolgreich bestanden."
+ },
"maintenance": {
"title": "Wartung & Daten",
"description": "Verwalten Sie Ihre lokalen Daten, löschen Sie Caches und sichern Sie Konversationen.",

diff --git a/meshchatx/src/frontend/locales/en.json b/meshchatx/src/frontend/locales/en.json
index e5273070..397af850 100644
--- a/meshchatx/src/frontend/locales/en.json
+++ b/meshchatx/src/frontend/locales/en.json
@@ -695,6 +695,21 @@
"installing": "Installing plugin..."
}
},
+ "selftest": {
+ "title": "System Self-Test",
+ "description": "Run diagnostic checks to ensure the core network stack, database, configurations, and storage permissions are functional.",
+ "run_test_btn": "Run Diagnostics",
+ "running": "Running Diagnostics...",
+ "stack_up": "Network Stack",
+ "config_good": "Configuration Integrity",
+ "db_good": "Database Connection",
+ "read_write": "Storage Read/Write",
+ "passed": "Passed",
+ "failed": "Failed",
+ "status_label": "Status",
+ "reason_label": "Reason",
+ "checks_completed": "All checks passed successfully."
+ },
"maintenance": {
"title": "Maintenance & Data",
"description": "Manage your local data, clear caches, and backup conversations.",

diff --git a/meshchatx/src/frontend/locales/es.json b/meshchatx/src/frontend/locales/es.json
index bed32b96..38d2b14f 100644
--- a/meshchatx/src/frontend/locales/es.json
+++ b/meshchatx/src/frontend/locales/es.json
@@ -695,6 +695,21 @@
"installing": "Instalando plugin..."
}
},
+ "selftest": {
+ "title": "Autoprueba del sistema",
+ "description": "Ejecute pruebas de diagnóstico para garantizar que la pila de red, la base de datos, las configuraciones y los permisos de almacenamiento funcionen.",
+ "run_test_btn": "Ejecutar diagnóstico",
+ "running": "Ejecutando diagnóstico...",
+ "stack_up": "Pila de red",
+ "config_good": "Integridad de configuración",
+ "db_good": "Conexión a la base de datos",
+ "read_write": "Lectura/escritura de almacenamiento",
+ "passed": "Aprobado",
+ "failed": "Fallido",
+ "status_label": "Estado",
+ "reason_label": "Motivo",
+ "checks_completed": "Todas las comprobaciones se completaron con éxito."
+ },
"maintenance": {
"title": "Datos de mantenimiento",
"description": "Gestione sus datos locales, jaulas claras y conversaciones de respaldo.",

diff --git a/meshchatx/src/frontend/locales/fi.json b/meshchatx/src/frontend/locales/fi.json
index fbbcdec3..4c84c06c 100644
--- a/meshchatx/src/frontend/locales/fi.json
+++ b/meshchatx/src/frontend/locales/fi.json
@@ -695,6 +695,21 @@
"installing": "Asennetaan lisäosaa..."
}
},
+ "selftest": {
+ "title": "Järjestelmän itsetestaus",
+ "description": "Suorita diagnostiikkatarkistuksia varmistaaksesi, että verkko-ohjelmisto, tietokanta, asetukset ja tallennusoikeudet toimivat oikein.",
+ "run_test_btn": "Suorita diagnostiikka",
+ "running": "Suoritetaan diagnostiikkaa...",
+ "stack_up": "Verkkopino",
+ "config_good": "Asetusten eheys",
+ "db_good": "Tietokantayhteys",
+ "read_write": "Tallennustilan luku/kirjoitus",
+ "passed": "Hyväksytty",
+ "failed": "Epäonnistunut",
+ "status_label": "Tila",
+ "reason_label": "Syy",
+ "checks_completed": "Kaikki tarkistukset suoritettiin onnistuneesti."
+ },
"maintenance": {
"title": "Ylläpito ja tiedot",
"description": "Hallinnoi paikallista dataa, pyyhi välimuisti ja varmuuskopioi keskustelut.",

diff --git a/meshchatx/src/frontend/locales/fr.json b/meshchatx/src/frontend/locales/fr.json
index 1b8e4871..e1ae0ba9 100644
--- a/meshchatx/src/frontend/locales/fr.json
+++ b/meshchatx/src/frontend/locales/fr.json
@@ -695,6 +695,21 @@
"installing": "Installation du plugin..."
}
},
+ "selftest": {
+ "title": "Auto-test du système",
+ "description": "Exécuter des contrôles de diagnostic pour s'assurer que la pile réseau, la base de données, les configurations et les autorisations de stockage fonctionnent.",
+ "run_test_btn": "Exécuter les diagnostics",
+ "running": "Diagnostic en cours...",
+ "stack_up": "Pile réseau",
+ "config_good": "Intégrité de la configuration",
+ "db_good": "Connexion à la base de données",
+ "read_write": "Lecture/Écriture stockage",
+ "passed": "Réussi",
+ "failed": "Échoué",
+ "status_label": "Statut",
+ "reason_label": "Raison",
+ "checks_completed": "Tous les contrôles ont réussi."
+ },
"maintenance": {
"title": "Maintenance et données",
"description": "Gérer vos données locales, effacer les caches et les conversations de sauvegarde.",

diff --git a/meshchatx/src/frontend/locales/it.json b/meshchatx/src/frontend/locales/it.json
index ba92ee73..acd7e228 100644
--- a/meshchatx/src/frontend/locales/it.json
+++ b/meshchatx/src/frontend/locales/it.json
@@ -695,6 +695,21 @@
"installing": "Installazione plugin..."
}
},
+ "selftest": {
+ "title": "Self-Test del Sistema",
+ "description": "Esegui controlli diagnostici per assicurarti che lo stack di rete, il database, le configurazioni e i permessi di archiviazione funzionino.",
+ "run_test_btn": "Esegui Diagnostica",
+ "running": "Esecuzione Diagnostica...",
+ "stack_up": "Stack di Rete",
+ "config_good": "Integrità Configurazione",
+ "db_good": "Connessione Database",
+ "read_write": "Lettura/Scrittura Archivio",
+ "passed": "Superato",
+ "failed": "Fallito",
+ "status_label": "Stato",
+ "reason_label": "Motivo",
+ "checks_completed": "Tutti i controlli sono stati superati con successo."
+ },
"maintenance": {
"title": "Manutenzione e Dati",
"description": "Gestisci i tuoi dati locali, svuota le cache e fai il backup delle conversazioni.",

diff --git a/meshchatx/src/frontend/locales/nl.json b/meshchatx/src/frontend/locales/nl.json
index 1a3cf73e..646a4b72 100644
--- a/meshchatx/src/frontend/locales/nl.json
+++ b/meshchatx/src/frontend/locales/nl.json
@@ -695,6 +695,21 @@
"installing": "Plugin installeren..."
}
},
+ "selftest": {
+ "title": "Systeem-zelftest",
+ "description": "Voer diagnostische controles uit om er zeker van te zijn dat de netwerkstack, database, configuraties en opslagmachtigingen functioneren.",
+ "run_test_btn": "Diagnostiek uitvoeren",
+ "running": "Diagnostiek uitvoeren...",
+ "stack_up": "Netwerkstack",
+ "config_good": "Configuratie-integriteit",
+ "db_good": "Databaseverbinding",
+ "read_write": "Opslag Lezen/Schrijven",
+ "passed": "Geslaagd",
+ "failed": "Mislukt",
+ "status_label": "Status",
+ "reason_label": "Reden",
+ "checks_completed": "Alle controles zijn succesvol geslaagd."
+ },
"maintenance": {
"title": "Onderhoud & gegevens",
"description": "Beheer uw lokale gegevens, duidelijke caches en back-upgesprekken.",

diff --git a/meshchatx/src/frontend/locales/ru.json b/meshchatx/src/frontend/locales/ru.json
index 136bb4b9..6fecd8e8 100644
--- a/meshchatx/src/frontend/locales/ru.json
+++ b/meshchatx/src/frontend/locales/ru.json
@@ -695,6 +695,21 @@
"installing": "Установка плагина..."
}
},
+ "selftest": {
+ "title": "Самодиагностика системы",
+ "description": "Запуск диагностических тестов для проверки работоспособности сетевого стека, базы данных, файлов конфигурации и прав доступа к хранилищу.",
+ "run_test_btn": "Запустить диагностику",
+ "running": "Выполняется диагностика...",
+ "stack_up": "Сетевой стек",
+ "config_good": "Целостность конфигурации",
+ "db_good": "Подключение к базе данных",
+ "read_write": "Чтение/запись в хранилище",
+ "passed": "Успешно",
+ "failed": "Ошибка",
+ "status_label": "Статус",
+ "reason_label": "Причина",
+ "checks_completed": "Все проверки выполнены успешно."
+ },
"maintenance": {
"title": "Обслуживание и данные",
"description": "Управляйте локальными данными, очищайте кэши и создавайте резервные копии разговоров.",

diff --git a/meshchatx/src/frontend/locales/zh.json b/meshchatx/src/frontend/locales/zh.json
index 76a4e9df..bd98d0c6 100644
--- a/meshchatx/src/frontend/locales/zh.json
+++ b/meshchatx/src/frontend/locales/zh.json
@@ -695,6 +695,21 @@
"installing": "正在安装插件..."
}
},
+ "selftest": {
+ "title": "系统自检",
+ "description": "运行诊断检查以确保核心网络栈、数据库、配置以及存储权限均正常运行。",
+ "run_test_btn": "运行诊断",
+ "running": "正在运行诊断...",
+ "stack_up": "网络栈",
+ "config_good": "配置完整性",
+ "db_good": "数据库连接",
+ "read_write": "存储读/写",
+ "passed": "已通过",
+ "failed": "已失败",
+ "status_label": "状态",
+ "reason_label": "原因",
+ "checks_completed": "所有检查已成功通过。"
+ },
"maintenance": {
"title": "数据维护",
"description": "管理您的本地数据、清除缓存和备份对话。",

diff --git a/tests/backend/api_json_contract_schemas.py b/tests/backend/api_json_contract_schemas.py
index 2ff23900..5508f93e 100644
--- a/tests/backend/api_json_contract_schemas.py
+++ b/tests/backend/api_json_contract_schemas.py
@@ -169,6 +169,7 @@ _SERVER_BIND_STATUS_SCHEMA: dict = {
"listen_port": {"type": ["integer", "null"]},
"https_enabled": {"type": "boolean"},
"is_loopback_bind": {"type": "boolean"},
+ "plugins_enabled": {"type": "boolean"},
"landlock_kernel_supported": {"type": "boolean"},
"landlock_requested": {"type": "boolean"},
"landlock_auto_enabled": {"type": "boolean"},
@@ -186,6 +187,28 @@ API_V1_STATUS_SCHEMA: dict = {
"additionalProperties": False,
}
+SELF_TEST_STATUS_ITEM_SCHEMA: dict = {
+ "type": "object",
+ "required": ["status", "reason"],
+ "properties": {
+ "status": {"type": "string", "enum": ["ok", "failed"]},
+ "reason": {"type": "string"},
+ },
+ "additionalProperties": False,
+}
+
+SELF_TEST_SCHEMA: dict = {
+ "type": "object",
+ "required": ["stack_up", "config_good", "db_good", "read_write_good"],
+ "properties": {
+ "stack_up": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "config_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "db_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "read_write_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ },
+ "additionalProperties": False,
+}
+
API_V1_APP_INFO_ENVELOPE_SCHEMA: dict = {
"type": "object",
"required": ["app_info"],

diff --git a/tests/backend/fixtures/http_api_routes.json b/tests/backend/fixtures/http_api_routes.json
index 31803155..ee0c01e1 100644
--- a/tests/backend/fixtures/http_api_routes.json
+++ b/tests/backend/fixtures/http_api_routes.json
@@ -1,1368 +1,1400 @@
{
- "routes": [
- {
- "method": "GET",
- "path": "/"
- },
- {
- "method": "GET",
- "path": "/api/v1/announce"
- },
- {
- "method": "GET",
- "path": "/api/v1/announces"
- },
- {
- "method": "POST",
- "path": "/api/v1/announces/query"
- },
- {
- "method": "GET",
- "path": "/api/v1/app/changelog"
- },
- {
- "method": "POST",
- "path": "/api/v1/app/changelog/seen"
- },
- {
- "method": "GET",
- "path": "/api/v1/app/info"
- },
- {
- "method": "POST",
- "path": "/api/v1/app/integrity/acknowledge"
- },
- {
- "method": "POST",
- "path": "/api/v1/app/shutdown"
- },
- {
- "method": "POST",
- "path": "/api/v1/app/tutorial/seen"
- },
- {
- "method": "GET",
- "path": "/api/v1/auth/csrf"
- },
- {
- "method": "POST",
- "path": "/api/v1/auth/login"
- },
- {
- "method": "POST",
- "path": "/api/v1/auth/logout"
- },
- {
- "method": "POST",
- "path": "/api/v1/auth/setup"
- },
- {
- "method": "GET",
- "path": "/api/v1/auth/status"
- },
- {
- "method": "GET",
- "path": "/api/v1/blocked-destinations"
- },
- {
- "method": "POST",
- "path": "/api/v1/blocked-destinations"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/blocked-destinations/{destination_hash}"
- },
- {
- "method": "POST",
- "path": "/api/v1/bots/announce"
- },
- {
- "method": "POST",
- "path": "/api/v1/bots/delete"
- },
- {
- "method": "GET",
- "path": "/api/v1/bots/export"
- },
- {
- "method": "POST",
- "path": "/api/v1/bots/restart"
- },
- {
- "method": "POST",
- "path": "/api/v1/bots/start"
- },
- {
- "method": "GET",
- "path": "/api/v1/bots/status"
- },
- {
- "method": "POST",
- "path": "/api/v1/bots/stop"
- },
- {
- "method": "GET",
- "path": "/api/v1/bots/subprocess-log"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/bots/update"
- },
- {
- "method": "GET",
- "path": "/api/v1/community-interfaces"
- },
- {
- "method": "POST",
- "path": "/api/v1/community-interfaces/refresh"
- },
- {
- "method": "GET",
- "path": "/api/v1/comports"
- },
- {
- "method": "GET",
- "path": "/api/v1/config"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/config"
- },
- {
- "method": "POST",
- "path": "/api/v1/database/backup"
- },
- {
- "method": "GET",
- "path": "/api/v1/database/backup/download"
- },
- {
- "method": "GET",
- "path": "/api/v1/database/backups"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/database/backups/{filename}"
- },
- {
- "method": "GET",
- "path": "/api/v1/database/backups/{filename}/download"
- },
- {
- "method": "GET",
- "path": "/api/v1/database/health"
- },
- {
- "method": "POST",
- "path": "/api/v1/database/recover"
- },
- {
- "method": "POST",
- "path": "/api/v1/database/restore"
- },
- {
- "method": "POST",
- "path": "/api/v1/database/snapshot"
- },
- {
- "method": "GET",
- "path": "/api/v1/database/snapshots"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/database/snapshots/{filename}"
- },
- {
- "method": "GET",
- "path": "/api/v1/database/snapshots/{filename}/download"
- },
- {
- "method": "POST",
- "path": "/api/v1/database/vacuum"
- },
- {
- "method": "GET",
- "path": "/api/v1/debug/access-attempts"
- },
- {
- "method": "GET",
- "path": "/api/v1/debug/logs"
- },
- {
- "method": "GET",
- "path": "/api/v1/destination/{destination_hash}/custom-display-name"
- },
- {
- "method": "POST",
- "path": "/api/v1/destination/{destination_hash}/custom-display-name/update"
- },
- {
- "method": "POST",
- "path": "/api/v1/destination/{destination_hash}/drop-path"
- },
- {
- "method": "GET",
- "path": "/api/v1/destination/{destination_hash}/lxmf-stamp-info"
- },
- {
- "method": "GET",
- "path": "/api/v1/destination/{destination_hash}/path"
- },
- {
- "method": "POST",
- "path": "/api/v1/destination/{destination_hash}/request-path"
- },
- {
- "method": "GET",
- "path": "/api/v1/destination/{destination_hash}/signal-metrics"
- },
- {
- "method": "GET",
- "path": "/api/v1/diagnostics/memory"
- },
- {
- "method": "GET",
- "path": "/api/v1/diagnostics/memory/gc"
- },
- {
- "method": "POST",
- "path": "/api/v1/diagnostics/memory/gc/collect"
- },
- {
- "method": "GET",
- "path": "/api/v1/diagnostics/memory/heap"
- },
- {
- "method": "GET",
- "path": "/api/v1/diagnostics/memory/referrers"
- },
- {
- "method": "POST",
- "path": "/api/v1/diagnostics/memory/reset"
- },
- {
- "method": "POST",
- "path": "/api/v1/diagnostics/memory/snapshot"
- },
- {
- "method": "GET",
- "path": "/api/v1/docs/export"
- },
- {
- "method": "GET",
- "path": "/api/v1/docs/export/reticulum"
- },
- {
- "method": "GET",
- "path": "/api/v1/docs/search"
- },
- {
- "method": "GET",
- "path": "/api/v1/docs/status"
- },
- {
- "method": "POST",
- "path": "/api/v1/docs/switch"
- },
- {
- "method": "POST",
- "path": "/api/v1/docs/upload"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/docs/version/{version}"
- },
- {
- "method": "GET",
- "path": "/api/v1/favourites"
- },
- {
- "method": "POST",
- "path": "/api/v1/favourites/add"
- },
- {
- "method": "POST",
- "path": "/api/v1/favourites/import"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/favourites/{destination_hash}"
- },
- {
- "method": "POST",
- "path": "/api/v1/favourites/{destination_hash}/rename"
- },
- {
- "method": "GET",
- "path": "/api/v1/gifs"
- },
- {
- "method": "POST",
- "path": "/api/v1/gifs"
- },
- {
- "method": "GET",
- "path": "/api/v1/gifs/export"
- },
- {
- "method": "POST",
- "path": "/api/v1/gifs/import"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/gifs/{gif_id}"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/gifs/{gif_id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/gifs/{gif_id}/image"
- },
- {
- "method": "POST",
- "path": "/api/v1/gifs/{gif_id}/use"
- },
- {
- "method": "GET",
- "path": "/api/v1/identities"
- },
- {
- "method": "POST",
- "path": "/api/v1/identities/create"
- },
- {
- "method": "GET",
- "path": "/api/v1/identities/export-all"
- },
- {
- "method": "POST",
- "path": "/api/v1/identities/switch"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/identities/{identity_hash}"
- },
- {
- "method": "GET",
- "path": "/api/v1/identity/backup/base32"
- },
- {
- "method": "GET",
- "path": "/api/v1/identity/backup/download"
- },
- {
- "method": "POST",
- "path": "/api/v1/identity/restore"
- },
- {
- "method": "GET",
- "path": "/api/v1/interface-stats"
- },
- {
- "method": "GET",
- "path": "/api/v1/licenses"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf-messages/attachment/{message_hash}/{attachment_type}"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/lxmf-messages/conversation/{destination_hash}"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf-messages/conversation/{destination_hash}"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf-messages/reactions"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf-messages/send"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/lxmf-messages/{hash}"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf-messages/{hash}/cancel"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf-messages/{hash}/spam"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf-messages/{message_hash}/uri"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf/conversation-pins"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf/conversation-pins/toggle"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf/conversations"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf/conversations/bulk-delete"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf/conversations/bulk-mark-as-read"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf/conversations/move-to-folder"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf/conversations/{destination_hash}/mark-as-read"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf/folders"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf/folders"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf/folders/export"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf/folders/import"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/lxmf/folders/{id}"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/lxmf/folders/{id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf/message-blocklist"
- },
- {
- "method": "PUT",
- "path": "/api/v1/lxmf/message-blocklist"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf/message-blocklist/export"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf/message-blocklist/import"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf/propagation-node/restart"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf/propagation-node/status"
- },
- {
- "method": "POST",
- "path": "/api/v1/lxmf/propagation-node/stop"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf/propagation-node/stop-sync"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf/propagation-node/sync"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf/propagation-nodes"
- },
- {
- "method": "GET",
- "path": "/api/v1/lxmf/sieve-filters"
- },
- {
- "method": "PUT",
- "path": "/api/v1/lxmf/sieve-filters"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/maintenance/announces"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/maintenance/archives"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/maintenance/docs/reticulum"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/maintenance/favourites"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/maintenance/gifs"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/maintenance/lxmf-icons"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/maintenance/messages"
- },
- {
- "method": "GET",
- "path": "/api/v1/maintenance/messages/export"
- },
- {
- "method": "POST",
- "path": "/api/v1/maintenance/messages/import"
- },
- {
- "method": "POST",
- "path": "/api/v1/maintenance/messages/import-file"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/maintenance/path-table"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/maintenance/stickers"
- },
- {
- "method": "GET",
- "path": "/api/v1/map/drawings"
- },
- {
- "method": "POST",
- "path": "/api/v1/map/drawings"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/map/drawings/{drawing_id}"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/map/drawings/{drawing_id}"
- },
- {
- "method": "POST",
- "path": "/api/v1/map/export"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/map/export/{export_id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/map/export/{export_id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/map/export/{export_id}/download"
- },
- {
- "method": "GET",
- "path": "/api/v1/map/mbtiles"
- },
- {
- "method": "POST",
- "path": "/api/v1/map/mbtiles/active"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/map/mbtiles/{filename}"
- },
- {
- "method": "GET",
- "path": "/api/v1/map/offline"
- },
- {
- "method": "POST",
- "path": "/api/v1/map/offline"
- },
- {
- "method": "GET",
- "path": "/api/v1/map/tiles/{z}/{x}/{y}"
- },
- {
- "method": "GET",
- "path": "/api/v1/meshchatx-docs/content"
- },
- {
- "method": "GET",
- "path": "/api/v1/meshchatx-docs/list"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/nomadnet/archives"
- },
- {
- "method": "GET",
- "path": "/api/v1/nomadnet/archives"
- },
- {
- "method": "POST",
- "path": "/api/v1/nomadnetwork/{destination_hash}/identify"
- },
- {
- "method": "GET",
- "path": "/api/v1/notifications"
- },
- {
- "method": "POST",
- "path": "/api/v1/notifications/mark-as-viewed"
- },
- {
- "method": "GET",
- "path": "/api/v1/page-nodes"
- },
- {
- "method": "POST",
- "path": "/api/v1/page-nodes"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/page-nodes/{node_id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/page-nodes/{node_id}"
- },
- {
- "method": "POST",
- "path": "/api/v1/page-nodes/{node_id}/announce"
- },
- {
- "method": "GET",
- "path": "/api/v1/page-nodes/{node_id}/files"
- },
- {
- "method": "POST",
- "path": "/api/v1/page-nodes/{node_id}/files"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/page-nodes/{node_id}/files/{file_name}"
- },
- {
- "method": "GET",
- "path": "/api/v1/page-nodes/{node_id}/pages"
- },
- {
- "method": "POST",
- "path": "/api/v1/page-nodes/{node_id}/pages"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/page-nodes/{node_id}/pages/{page_name}"
- },
- {
- "method": "GET",
- "path": "/api/v1/page-nodes/{node_id}/pages/{page_name}"
- },
- {
- "method": "PUT",
- "path": "/api/v1/page-nodes/{node_id}/rename"
- },
- {
- "method": "POST",
- "path": "/api/v1/page-nodes/{node_id}/start"
- },
- {
- "method": "POST",
- "path": "/api/v1/page-nodes/{node_id}/stop"
- },
- {
- "method": "GET",
- "path": "/api/v1/path-table"
- },
- {
- "method": "POST",
- "path": "/api/v1/path-table"
- },
- {
- "method": "GET",
- "path": "/api/v1/ping/{destination_hash}/lxmf.delivery"
- },
- {
- "method": "GET",
- "path": "/api/v1/plugins"
- },
- {
- "method": "POST",
- "path": "/api/v1/plugins/install"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/plugins/{plugin_id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/plugins/{plugin_id}/asset/{asset_path:.*}"
- },
- {
- "method": "POST",
- "path": "/api/v1/plugins/{plugin_id}/disable"
- },
- {
- "method": "POST",
- "path": "/api/v1/plugins/{plugin_id}/enable"
- },
- {
- "method": "POST",
- "path": "/api/v1/plugins/{plugin_id}/invoke"
- },
- {
- "method": "POST",
- "path": "/api/v1/repository-server/http/restart"
- },
- {
- "method": "POST",
- "path": "/api/v1/repository-server/http/start"
- },
- {
- "method": "POST",
- "path": "/api/v1/repository-server/http/stop"
- },
- {
- "method": "GET",
- "path": "/api/v1/repository-server/list"
- },
- {
- "method": "POST",
- "path": "/api/v1/repository-server/refresh-bundled"
- },
- {
- "method": "GET",
- "path": "/api/v1/repository-server/status"
- },
- {
- "method": "POST",
- "path": "/api/v1/repository-server/upload"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/repository-server/upload/{name}"
- },
- {
- "method": "GET",
- "path": "/api/v1/reticulum/blackhole"
- },
- {
- "method": "GET",
- "path": "/api/v1/reticulum/config/raw"
- },
- {
- "method": "PUT",
- "path": "/api/v1/reticulum/config/raw"
- },
- {
- "method": "POST",
- "path": "/api/v1/reticulum/config/reset"
- },
- {
- "method": "POST",
- "path": "/api/v1/reticulum/disable-transport"
- },
- {
- "method": "GET",
- "path": "/api/v1/reticulum/discovered-interfaces"
- },
- {
- "method": "GET",
- "path": "/api/v1/reticulum/discovery"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/reticulum/discovery"
- },
- {
- "method": "POST",
- "path": "/api/v1/reticulum/enable-transport"
- },
- {
- "method": "GET",
- "path": "/api/v1/reticulum/interfaces"
- },
- {
- "method": "POST",
- "path": "/api/v1/reticulum/interfaces/add"
- },
- {
- "method": "POST",
- "path": "/api/v1/reticulum/interfaces/delete"
- },
- {
- "method": "POST",
- "path": "/api/v1/reticulum/interfaces/disable"
- },
- {
- "method": "POST",
- "path": "/api/v1/reticulum/interfaces/enable"
- },
- {
- "method": "POST",
- "path": "/api/v1/reticulum/interfaces/export"
- },
- {
- "method": "POST",
- "path": "/api/v1/reticulum/interfaces/import"
- },
- {
- "method": "POST",
- "path": "/api/v1/reticulum/interfaces/import-preview"
- },
- {
- "method": "POST",
- "path": "/api/v1/reticulum/reload"
- },
- {
- "method": "POST",
- "path": "/api/v1/rncp/fetch"
- },
- {
- "method": "POST",
- "path": "/api/v1/rncp/listen"
- },
- {
- "method": "POST",
- "path": "/api/v1/rncp/send"
- },
- {
- "method": "GET",
- "path": "/api/v1/rncp/status"
- },
- {
- "method": "POST",
- "path": "/api/v1/rncp/stop"
- },
- {
- "method": "GET",
- "path": "/api/v1/rncp/transfer/{transfer_id}"
- },
- {
- "method": "POST",
- "path": "/api/v1/rnpath/drop"
- },
- {
- "method": "POST",
- "path": "/api/v1/rnpath/drop-queues"
- },
- {
- "method": "POST",
- "path": "/api/v1/rnpath/drop-via"
- },
- {
- "method": "GET",
- "path": "/api/v1/rnpath/rates"
- },
- {
- "method": "POST",
- "path": "/api/v1/rnpath/request"
- },
- {
- "method": "GET",
- "path": "/api/v1/rnpath/table"
- },
- {
- "method": "GET",
- "path": "/api/v1/rnpath/trace/{destination_hash}"
- },
- {
- "method": "POST",
- "path": "/api/v1/rnprobe"
- },
- {
- "method": "GET",
- "path": "/api/v1/rnsh/sessions"
- },
- {
- "method": "POST",
- "path": "/api/v1/rnsh/sessions"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/rnsh/sessions/{session_id}"
- },
- {
- "method": "POST",
- "path": "/api/v1/rnsh/sessions/{session_id}/clear"
- },
- {
- "method": "POST",
- "path": "/api/v1/rnsh/sessions/{session_id}/input"
- },
- {
- "method": "GET",
- "path": "/api/v1/rnsh/sessions/{session_id}/output"
- },
- {
- "method": "POST",
- "path": "/api/v1/rnsh/sessions/{session_id}/resize"
- },
- {
- "method": "POST",
- "path": "/api/v1/rnsh/sessions/{session_id}/start"
- },
- {
- "method": "POST",
- "path": "/api/v1/rnsh/sessions/{session_id}/stop"
- },
- {
- "method": "GET",
- "path": "/api/v1/rnstatus"
- },
- {
- "method": "GET",
- "path": "/api/v1/rrc/hubs"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/hubs"
- },
- {
- "method": "PUT",
- "path": "/api/v1/rrc/hubs/order"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/rrc/hubs/{hub_hash}"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/rrc/hubs/{hub_hash}"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/hubs/{hub_hash}/command"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/hubs/{hub_hash}/connect"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/hubs/{hub_hash}/disconnect"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/hubs/{hub_hash}/rooms"
- },
- {
- "method": "PUT",
- "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/order"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/{room}"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/{room}/messages"
- },
- {
- "method": "GET",
- "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/{room}/messages"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/{room}/messages"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/{room}/read"
- },
- {
- "method": "GET",
- "path": "/api/v1/rrc/servers"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/servers"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/rrc/servers/{hub_id}"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/rrc/servers/{hub_id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/rrc/servers/{hub_id}/activity"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/servers/{hub_id}/announce"
- },
- {
- "method": "GET",
- "path": "/api/v1/rrc/servers/{hub_id}/members"
- },
- {
- "method": "GET",
- "path": "/api/v1/rrc/servers/{hub_id}/messages"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/servers/{hub_id}/moderate"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/servers/{hub_id}/rooms"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/rrc/servers/{hub_id}/rooms/{room}"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/servers/{hub_id}/start"
- },
- {
- "method": "POST",
- "path": "/api/v1/rrc/servers/{hub_id}/stop"
- },
- {
- "method": "GET",
- "path": "/api/v1/server/security"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/server/security"
- },
- {
- "method": "POST",
- "path": "/api/v1/setup/storage-migration"
- },
- {
- "method": "GET",
- "path": "/api/v1/spam-keywords"
- },
- {
- "method": "POST",
- "path": "/api/v1/spam-keywords"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/spam-keywords/{keyword_id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/status"
- },
- {
- "method": "GET",
- "path": "/api/v1/sticker-packs"
- },
- {
- "method": "POST",
- "path": "/api/v1/sticker-packs"
- },
- {
- "method": "POST",
- "path": "/api/v1/sticker-packs/install"
- },
- {
- "method": "POST",
- "path": "/api/v1/sticker-packs/reorder"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/sticker-packs/{pack_id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/sticker-packs/{pack_id}"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/sticker-packs/{pack_id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/sticker-packs/{pack_id}/export"
- },
- {
- "method": "GET",
- "path": "/api/v1/stickers"
- },
- {
- "method": "POST",
- "path": "/api/v1/stickers"
- },
- {
- "method": "GET",
- "path": "/api/v1/stickers/export"
- },
- {
- "method": "POST",
- "path": "/api/v1/stickers/import"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/stickers/{sticker_id}"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/stickers/{sticker_id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/stickers/{sticker_id}/image"
- },
- {
- "method": "GET",
- "path": "/api/v1/system/network-interfaces"
- },
- {
- "method": "GET",
- "path": "/api/v1/telemetry/history/{destination_hash}"
- },
- {
- "method": "GET",
- "path": "/api/v1/telemetry/latest/{destination_hash}"
- },
- {
- "method": "GET",
- "path": "/api/v1/telemetry/peers"
- },
- {
- "method": "GET",
- "path": "/api/v1/telemetry/tracking"
- },
- {
- "method": "POST",
- "path": "/api/v1/telemetry/tracking/{destination_hash}/toggle"
- },
- {
- "method": "GET",
- "path": "/api/v1/telemetry/trusted-peers"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/answer"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/audio-profiles"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/call/{identity_hash}"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/contacts"
- },
- {
- "method": "POST",
- "path": "/api/v1/telephone/contacts"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/contacts/check/{identity_hash}"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/contacts/export"
- },
- {
- "method": "POST",
- "path": "/api/v1/telephone/contacts/import"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/telephone/contacts/{id}"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/telephone/contacts/{id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/hangup"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/telephone/history"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/history"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/mute-receive"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/mute-transmit"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/recordings"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/telephone/recordings/{id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/recordings/{id}/audio/{side}"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/ringtones"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/ringtones/status"
- },
- {
- "method": "POST",
- "path": "/api/v1/telephone/ringtones/upload"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/telephone/ringtones/{id}"
- },
- {
- "method": "PATCH",
- "path": "/api/v1/telephone/ringtones/{id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/ringtones/{id}/audio"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/send-to-voicemail"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/status"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/switch-audio-profile/{profile_id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/unmute-receive"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/unmute-transmit"
- },
- {
- "method": "POST",
- "path": "/api/v1/telephone/voicemail/generate-greeting"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/telephone/voicemail/greeting"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/voicemail/greeting/audio"
- },
- {
- "method": "POST",
- "path": "/api/v1/telephone/voicemail/greeting/record/start"
- },
- {
- "method": "POST",
- "path": "/api/v1/telephone/voicemail/greeting/record/stop"
- },
- {
- "method": "POST",
- "path": "/api/v1/telephone/voicemail/greeting/upload"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/voicemail/status"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/voicemails"
- },
- {
- "method": "DELETE",
- "path": "/api/v1/telephone/voicemails/{id}"
- },
- {
- "method": "GET",
- "path": "/api/v1/telephone/voicemails/{id}/audio"
- },
- {
- "method": "POST",
- "path": "/api/v1/telephone/voicemails/{id}/read"
- },
- {
- "method": "GET",
- "path": "/api/v1/tools/micron-parser-go-release"
- },
- {
- "method": "GET",
- "path": "/api/v1/tools/rnode/download_firmware"
- },
- {
- "method": "GET",
- "path": "/api/v1/tools/rnode/latest_release"
- },
- {
- "method": "POST",
- "path": "/api/v1/translator/install-languages"
- },
- {
- "method": "GET",
- "path": "/api/v1/translator/languages"
- },
- {
- "method": "POST",
- "path": "/api/v1/translator/translate"
- },
- {
- "method": "GET",
- "path": "/call.html"
- },
- {
- "method": "GET",
- "path": "/manifest.json"
- },
- {
- "method": "GET",
- "path": "/service-worker.js"
- },
- {
- "method": "GET",
- "path": "/ws"
- },
- {
- "method": "GET",
- "path": "/ws/telephone/audio"
- }
- ]
+ "routes": [
+ {
+ "method": "GET",
+ "path": "/"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/announce"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/announces"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/announces/query"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/app/changelog"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/app/changelog/seen"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/app/info"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/app/integrity/acknowledge"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/app/shutdown"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/app/tutorial/seen"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/auth/csrf"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/auth/login"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/auth/logout"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/auth/setup"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/auth/status"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/blocked-destinations"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/blocked-destinations"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/blocked-destinations/{destination_hash}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/bots/announce"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/bots/delete"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/bots/export"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/bots/restart"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/bots/start"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/bots/status"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/bots/stop"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/bots/subprocess-log"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/bots/update"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/community-interfaces"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/community-interfaces/refresh"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/comports"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/config"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/config"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/database/backup"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/database/backup/download"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/database/backups"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/database/backups/{filename}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/database/backups/{filename}/download"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/database/health"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/database/recover"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/database/restore"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/database/snapshot"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/database/snapshots"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/database/snapshots/{filename}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/database/snapshots/{filename}/download"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/database/vacuum"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/debug/access-attempts"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/debug/logs"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/destination/{destination_hash}/custom-display-name"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/destination/{destination_hash}/custom-display-name/update"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/destination/{destination_hash}/drop-path"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/destination/{destination_hash}/lxmf-stamp-info"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/destination/{destination_hash}/path"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/destination/{destination_hash}/request-path"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/destination/{destination_hash}/signal-metrics"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/diagnostics/memory"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/diagnostics/memory/gc"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/diagnostics/memory/gc/collect"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/diagnostics/memory/heap"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/diagnostics/memory/referrers"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/diagnostics/memory/reset"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/diagnostics/memory/snapshot"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/docs/export"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/docs/export/reticulum"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/docs/search"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/docs/status"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/docs/switch"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/docs/upload"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/docs/version/{version}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/favourites"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/favourites/add"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/favourites/import"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/favourites/{destination_hash}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/favourites/{destination_hash}/rename"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/gifs"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/gifs"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/gifs/export"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/gifs/import"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/gifs/{gif_id}"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/gifs/{gif_id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/gifs/{gif_id}/image"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/gifs/{gif_id}/use"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/identities"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/identities/create"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/identities/export-all"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/identities/switch"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/identities/{identity_hash}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/identity/backup/base32"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/identity/backup/download"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/identity/restore"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/interface-stats"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/licenses"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf-messages/attachment/{message_hash}/{attachment_type}"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/lxmf-messages/conversation/{destination_hash}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf-messages/conversation/{destination_hash}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf-messages/reactions"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf-messages/send"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/lxmf-messages/{hash}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf-messages/{hash}/cancel"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf-messages/{hash}/spam"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf-messages/{message_hash}/uri"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf/conversation-pins"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf/conversation-pins/toggle"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf/conversations"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf/conversations/bulk-delete"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf/conversations/bulk-mark-as-read"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf/conversations/move-to-folder"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf/conversations/{destination_hash}/mark-as-read"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf/folders"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf/folders"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf/folders/export"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf/folders/import"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/lxmf/folders/{id}"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/lxmf/folders/{id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf/message-blocklist"
+ },
+ {
+ "method": "PUT",
+ "path": "/api/v1/lxmf/message-blocklist"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf/message-blocklist/export"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf/message-blocklist/import"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf/propagation-node/restart"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf/propagation-node/status"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/lxmf/propagation-node/stop"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf/propagation-node/stop-sync"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf/propagation-node/sync"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf/propagation-nodes"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/lxmf/sieve-filters"
+ },
+ {
+ "method": "PUT",
+ "path": "/api/v1/lxmf/sieve-filters"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/maintenance/announces"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/maintenance/archives"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/maintenance/docs/reticulum"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/maintenance/favourites"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/maintenance/gifs"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/maintenance/lxmf-icons"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/maintenance/messages"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/maintenance/messages/export"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/maintenance/messages/import"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/maintenance/messages/import-file"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/maintenance/path-table"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/maintenance/stickers"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/map/drawings"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/map/drawings"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/map/drawings/{drawing_id}"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/map/drawings/{drawing_id}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/map/export"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/map/export/{export_id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/map/export/{export_id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/map/export/{export_id}/download"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/map/mbtiles"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/map/mbtiles/active"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/map/mbtiles/{filename}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/map/offline"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/map/offline"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/map/tiles/{z}/{x}/{y}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/meshchatx-docs/content"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/meshchatx-docs/list"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/nomadnet/archives"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/nomadnet/archives"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/nomadnetwork/{destination_hash}/identify"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/notification-sounds"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/notification-sounds/status"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/notification-sounds/upload"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/notification-sounds/{id}"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/notification-sounds/{id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/notification-sounds/{id}/audio"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/notifications"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/notifications/mark-as-viewed"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/page-nodes"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/page-nodes"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/page-nodes/{node_id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/page-nodes/{node_id}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/page-nodes/{node_id}/announce"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/page-nodes/{node_id}/files"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/page-nodes/{node_id}/files"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/page-nodes/{node_id}/files/{file_name}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/page-nodes/{node_id}/pages"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/page-nodes/{node_id}/pages"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/page-nodes/{node_id}/pages/{page_name}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/page-nodes/{node_id}/pages/{page_name}"
+ },
+ {
+ "method": "PUT",
+ "path": "/api/v1/page-nodes/{node_id}/rename"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/page-nodes/{node_id}/start"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/page-nodes/{node_id}/stop"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/path-table"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/path-table"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/ping/{destination_hash}/lxmf.delivery"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/plugins"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/plugins/install"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/plugins/{plugin_id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/plugins/{plugin_id}/asset/{asset_path:.*}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/plugins/{plugin_id}/disable"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/plugins/{plugin_id}/enable"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/plugins/{plugin_id}/invoke"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/plugins/{plugin_id}/report-failure"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/repository-server/http/restart"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/repository-server/http/start"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/repository-server/http/stop"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/repository-server/list"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/repository-server/refresh-bundled"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/repository-server/status"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/repository-server/upload"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/repository-server/upload/{name}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/reticulum/blackhole"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/reticulum/config/raw"
+ },
+ {
+ "method": "PUT",
+ "path": "/api/v1/reticulum/config/raw"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/reticulum/config/reset"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/reticulum/disable-transport"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/reticulum/discovered-interfaces"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/reticulum/discovery"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/reticulum/discovery"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/reticulum/enable-transport"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/reticulum/interfaces"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/reticulum/interfaces/add"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/reticulum/interfaces/delete"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/reticulum/interfaces/disable"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/reticulum/interfaces/enable"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/reticulum/interfaces/export"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/reticulum/interfaces/import"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/reticulum/interfaces/import-preview"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/reticulum/reload"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rncp/fetch"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rncp/listen"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rncp/send"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rncp/status"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rncp/stop"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rncp/transfer/{transfer_id}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rnpath/drop"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rnpath/drop-queues"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rnpath/drop-via"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rnpath/rates"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rnpath/request"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rnpath/table"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rnpath/trace/{destination_hash}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rnprobe"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rnsh/sessions"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rnsh/sessions"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/rnsh/sessions/{session_id}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rnsh/sessions/{session_id}/clear"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rnsh/sessions/{session_id}/input"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rnsh/sessions/{session_id}/output"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rnsh/sessions/{session_id}/resize"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rnsh/sessions/{session_id}/start"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rnsh/sessions/{session_id}/stop"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rnstatus"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rrc/hubs"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/hubs"
+ },
+ {
+ "method": "PUT",
+ "path": "/api/v1/rrc/hubs/order"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/rrc/hubs/{hub_hash}"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/rrc/hubs/{hub_hash}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/hubs/{hub_hash}/command"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/hubs/{hub_hash}/connect"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/hubs/{hub_hash}/disconnect"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/hubs/{hub_hash}/rooms"
+ },
+ {
+ "method": "PUT",
+ "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/order"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/{room}"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/{room}/messages"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/{room}/messages"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/{room}/messages"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/hubs/{hub_hash}/rooms/{room}/read"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rrc/servers"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/servers"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/rrc/servers/{hub_id}"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/rrc/servers/{hub_id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rrc/servers/{hub_id}/activity"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/servers/{hub_id}/announce"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rrc/servers/{hub_id}/members"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/rrc/servers/{hub_id}/messages"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/servers/{hub_id}/moderate"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/servers/{hub_id}/rooms"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/rrc/servers/{hub_id}/rooms/{room}"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/servers/{hub_id}/start"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/rrc/servers/{hub_id}/stop"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/self-test"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/server/security"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/server/security"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/setup/storage-migration"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/spam-keywords"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/spam-keywords"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/spam-keywords/{keyword_id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/status"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/sticker-packs"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/sticker-packs"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/sticker-packs/install"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/sticker-packs/reorder"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/sticker-packs/{pack_id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/sticker-packs/{pack_id}"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/sticker-packs/{pack_id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/sticker-packs/{pack_id}/export"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/stickers"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/stickers"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/stickers/export"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/stickers/import"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/stickers/{sticker_id}"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/stickers/{sticker_id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/stickers/{sticker_id}/image"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/system/network-interfaces"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telemetry/history/{destination_hash}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telemetry/latest/{destination_hash}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telemetry/peers"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telemetry/tracking"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/telemetry/tracking/{destination_hash}/toggle"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telemetry/trusted-peers"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/answer"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/audio-profiles"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/call/{identity_hash}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/contacts"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/telephone/contacts"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/contacts/check/{identity_hash}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/contacts/export"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/telephone/contacts/import"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/telephone/contacts/{id}"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/telephone/contacts/{id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/hangup"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/telephone/history"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/history"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/mute-receive"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/mute-transmit"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/recordings"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/telephone/recordings/{id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/recordings/{id}/audio/{side}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/ringtones"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/ringtones/status"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/telephone/ringtones/upload"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/telephone/ringtones/{id}"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/telephone/ringtones/{id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/ringtones/{id}/audio"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/send-to-voicemail"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/status"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/switch-audio-profile/{profile_id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/unmute-receive"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/unmute-transmit"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/telephone/voicemail/generate-greeting"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/telephone/voicemail/greeting"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/voicemail/greeting/audio"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/telephone/voicemail/greeting/record/start"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/telephone/voicemail/greeting/record/stop"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/telephone/voicemail/greeting/upload"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/voicemail/status"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/voicemails"
+ },
+ {
+ "method": "DELETE",
+ "path": "/api/v1/telephone/voicemails/{id}"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/telephone/voicemails/{id}/audio"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/telephone/voicemails/{id}/read"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/tools/micron-parser-go-release"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/tools/rnode/download_firmware"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/tools/rnode/latest_release"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/translator/install-languages"
+ },
+ {
+ "method": "GET",
+ "path": "/api/v1/translator/languages"
+ },
+ {
+ "method": "POST",
+ "path": "/api/v1/translator/translate"
+ },
+ {
+ "method": "GET",
+ "path": "/call.html"
+ },
+ {
+ "method": "GET",
+ "path": "/manifest.json"
+ },
+ {
+ "method": "GET",
+ "path": "/service-worker.js"
+ },
+ {
+ "method": "GET",
+ "path": "/ws"
+ },
+ {
+ "method": "GET",
+ "path": "/ws/telephone/audio"
+ }
+ ]
}

diff --git a/tests/backend/http_api_response_registry.py b/tests/backend/http_api_response_registry.py
index ac0e03e3..e6e899f3 100644
--- a/tests/backend/http_api_response_registry.py
+++ b/tests/backend/http_api_response_registry.py
@@ -9,6 +9,7 @@ import re
from tests.backend.api_json_contract_schemas import (
API_V1_APP_INFO_ENVELOPE_SCHEMA,
API_V1_STATUS_SCHEMA,
+ SELF_TEST_SCHEMA,
AUTH_STATUS_SCHEMA,
TELEPHONE_CONTACT_CHECK_SCHEMA,
TELEPHONE_CONTACTS_LIST_SCHEMA,
@@ -121,6 +122,7 @@ _PAGE_NAME = "index.mu"
HTTP_JSON_GET_CONTRACTS: tuple[HttpJsonContract, ...] = (
HttpJsonContract("GET", "/api/v1/status", API_V1_STATUS_SCHEMA),
+ HttpJsonContract("GET", "/api/v1/self-test", SELF_TEST_SCHEMA),
HttpJsonContract("GET", "/api/v1/app/info", API_V1_APP_INFO_ENVELOPE_SCHEMA),
HttpJsonContract("GET", "/api/v1/app/changelog", CHANGELOG_SCHEMA),
HttpJsonContract("GET", "/api/v1/auth/status", AUTH_STATUS_SCHEMA),

diff --git a/tests/e2e/smoke.spec.js b/tests/e2e/smoke.spec.js
index 92af1979..e1584c2e 100644
--- a/tests/e2e/smoke.spec.js
+++ b/tests/e2e/smoke.spec.js
@@ -11,6 +11,16 @@ test.describe("MeshChatX E2E (Vite + Python backend)", () => {
expect(body.status).toBe("ok");
});
+ test("backend /api/v1/self-test returns success for all checks", async ({ request }) => {
+ const res = await request.get("/api/v1/self-test");
+ expect(res.ok()).toBeTruthy();
+ const body = await res.json();
+ expect(body.stack_up.status).toBe("ok");
+ expect(body.config_good.status).toBe("ok");
+ expect(body.db_good.status).toBe("ok");
+ expect(body.read_write_good.status).toBe("ok");
+ });
+
test("backend /api/v1/app/info returns version JSON (direct backend)", async ({ request }) => {
const res = await request.get(`${E2E_BACKEND_ORIGIN}/api/v1/app/info`);
expect(res.ok()).toBeTruthy();


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────